package git.soulbgm.job;

import java.util.Date;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import git.soulbgm.common.enums.Status;
import git.soulbgm.common.okhttp.OkHttpUtil;
import git.soulbgm.config.SysConfig;
import git.soulbgm.pojo.NodeInfo;
import git.soulbgm.pojo.NodeStatus;
import git.soulbgm.pojo.ServiceStarting;
import git.soulbgm.service.*;
import git.soulbgm.utils.LogHelper;
import git.soulbgm.utils.StringUtil;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static git.soulbgm.common.MonitorConstants.*;

/**
 * 设备节点健康监测任务
 *
 * @author SoulBGM
 * @date 2021/01/29
 */
@Service
public class NodeHealthMonitorTask {

    private static final LogHelper LOG = new LogHelper(NodeHealthMonitorTask.class);

    private final NodeInfoService nodeInfoService;
    private final NodeStatusService nodeStatusService;
    private final ServiceStartingService serviceStartingService;
    private final StartupService startupService;
    private final AlarmInfoReport alarmInfoReport;
    private final Integer currentRegionCode;

    public NodeHealthMonitorTask(NodeInfoService nodeInfoService, NodeStatusService nodeStatusService, ServiceStartingService serviceStartingService, StartupService startupService, AlarmInfoReport alarmInfoReport, SysConfig config) {
        this.nodeInfoService = nodeInfoService;
        this.nodeStatusService = nodeStatusService;
        this.serviceStartingService = serviceStartingService;
        this.startupService = startupService;
        this.alarmInfoReport = alarmInfoReport;
        this.currentRegionCode = config.getRegionCode();
    }

    @Scheduled(cron = "${jobs.map.nodeHealthMonitoring.cron}")
    @SchedulerLock(name = "${jobs.map.nodeHealthMonitoring.name}", lockAtMostFor = "${jobs.map.nodeHealthMonitoring.lockAtMostFor}", lockAtLeastFor = "${jobs.map.nodeHealthMonitoring.lockAtLeastFor}")
    public void execute() throws Exception {
        Date collectTime = new Date();
        Map<Long, NodeInfo> map = nodeInfoService.getCacheNodeInfoAll().stream()
                .filter(o -> Status.NORMAL.code.equals(o.getStatus()) && MONITOR_MODE_PASSIVITY == o.getMonitorMode()).collect(Collectors.toMap(NodeInfo::getId, Function.identity()));
        if (map.isEmpty()) {
            return;
        }
        List<NodeStatus> statusList = map.values().stream()
                .map(o -> getNodeStatus(o, collectTime)).collect(Collectors.toList());
        int size = map.size();
        CountDownLatch latch = new CountDownLatch(size);
        for (NodeStatus nodeStatus : statusList) {
            NodeInfo info = map.get(nodeStatus.getId());
            String url = StringUtil.format("http://{}:{}{}", info.getIp(), info.getPort(), HEALTH_CHECK_URL);
            OkHttpUtil.get(url, new NodeHealthMonitorCallback(nodeStatus, latch));
        }

        boolean isExecuteComplete = latch.await(10L * size, TimeUnit.SECONDS);
        if (!isExecuteComplete) {
            LOG.error("执行节点健康监测时间超时");
        }

        for (NodeStatus nodeStatus : statusList) {
            NodeInfo info = map.get(nodeStatus.getId());
            nodeStatus.setStatus(checkStartup(nodeStatus, info));
            recordAlarm(nodeStatus);
        }

        nodeStatusService.reset(statusList);

    }

    /**
     * 获得节点状态
     *
     * @param info        信息
     * @param collectTime 收集时间
     * @return {@link NodeStatus}
     */
    private NodeStatus getNodeStatus(NodeInfo info, Date collectTime) {
        NodeStatus nodeStatus = new NodeStatus();
        nodeStatus.setId(info.getId());
        nodeStatus.setNodeCode(info.getNodeCode());
        nodeStatus.setDeployIp(info.getIp());
        nodeStatus.setDetectionTime(collectTime);
        nodeStatus.setRegionCode(currentRegionCode);
        nodeStatus.setStatus(Status.NOT_INITIATED.code);
        return nodeStatus;
    }

    /**
     * 检查启动
     *
     * @param nodeStatus 节点状态
     * @param info       节点信息
     */
    private Integer checkStartup(NodeStatus nodeStatus, NodeInfo info) {
        Integer status = nodeStatus.getStatus();
        Integer selfStart = info.getSelfStart();

        ServiceStarting starting = new ServiceStarting(nodeStatus.getNodeCode(),
                MONITOR_AGENT,
                RULE_TYPE_NODE,
                nodeStatus.getDeployIp());
        if (Status.NOT_INITIATED.code.equals(status)) {
            // 查看 启动表中是否有记录
            ServiceStarting serviceStarting = serviceStartingService.getOne(new QueryWrapper<>(starting));
            // 如果没有则添加
            if (serviceStarting == null && Status.YES.code.equals(selfStart)) {
                starting.setStartupTime(new Date());
                serviceStartingService.save(starting);
                startupService.asyncStartupNode(nodeStatus.getNodeCode());
                status = Status.STARTING.code;
            } else if (serviceStarting != null) {
                // 如果有则判断是否超过预计时间
                Date startUpTime = serviceStarting.getStartupTime();
                Date nowDate = new Date();
                long difference = nowDate.getTime() - startUpTime.getTime();
                if ((difference / ONE_SECONDS_MILLISECOND) >= ESTIMATED_STARTUP_TIME_DEFAULT_AGENT) {
                    // 如果超过则设置状态为 未启动
                    status = Status.NOT_INITIATED.code;
                } else {
                    // 如果没有超过则设置状态为 启动中
                    status = Status.STARTING.code;
                }
            } else {
                // 不需要自动启动则设置状态为 未启动
                status = Status.NOT_INITIATED.code;
            }
        } else if (Status.NORMAL.code.equals(status)) {
            // 删除 启动表中的记录
            serviceStartingService.remove(new QueryWrapper<>(starting));
        }
        return status;
    }

    /**
     * 记录报警
     *
     * @param nodeStatus 节点状态
     */
    public void recordAlarm(NodeStatus nodeStatus) {
        Integer status = nodeStatus.getStatus();
        if (Status.STARTING.code.equals(status)) {
            // 设备重启
            String alarmContent = StringUtil.format("IP为 {}的服务器监控客户端宕机，现在正在尝试重启", nodeStatus.getDeployIp());
            LOG.warn(alarmContent);
            alarmInfoReport.nodeRebootAlarm(nodeStatus.getNodeCode(), alarmContent);
        } else if (Status.NOT_INITIATED.code.equals(status)) {
            // 设备宕机
            String alarmContent = StringUtil.format("IP为 {}的服务器监控客户端，尝试重启没有成功现在处于宕机", nodeStatus.getDeployIp());
            LOG.warn(alarmContent);
            alarmInfoReport.nodeShutdownAlarm(nodeStatus.getNodeCode(), alarmContent);
        } else if (Status.NORMAL.code.equals(status)) {
            // 设备正常
            alarmInfoReport.nodeStartingClearAlarm(nodeStatus.getNodeCode());
        }
    }

}

/**
 * 设备节点健康监测回调
 *
 * @author SoulBGM
 * @date 2023-01-06
 */
class NodeHealthMonitorCallback implements Callback {

    private static final String UP = "up";

    private final NodeStatus nodeStatus;
    private final CountDownLatch latch;

    public NodeHealthMonitorCallback(NodeStatus nodeStatus, CountDownLatch latch) {
        this.nodeStatus = nodeStatus;
        this.latch = latch;
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        ResponseBody body = response.body();
        int status = Status.NOT_INITIATED.code;
        if (body != null) {
            String result = body.string();
            if (UP.equals(result)) {
                status = Status.NORMAL.code;
            }
        }
        nodeStatus.setStatus(status);
        latch.countDown();
    }

    @Override
    public void onFailure(Call call, IOException e) {
        latch.countDown();
    }

}